package edu.cs.usask.ca.silver.plugin.rcp.dao;

import java.io.File;
import java.sql.SQLDataException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.codec.net.BCodec;
import org.apache.log4j.Logger;
import org.eclipse.ui.IWorkbench;

import edu.cs.usask.ca.silver.api.businesslogic.AssociatedFileI;
import edu.cs.usask.ca.silver.api.businesslogic.GroupI;
import edu.cs.usask.ca.silver.api.businesslogic.ModelProjectI;
import edu.cs.usask.ca.silver.api.businesslogic.ModelVersionI;
import edu.cs.usask.ca.silver.api.businesslogic.ParameterI;
import edu.cs.usask.ca.silver.api.businesslogic.ScenarioCollectionI;
import edu.cs.usask.ca.silver.api.businesslogic.ScenarioI;
import edu.cs.usask.ca.silver.api.businesslogic.SilverServiceI;
import edu.cs.usask.ca.silver.api.businesslogic.UserI;
import edu.cs.usask.ca.silver.api.persistence.StorageI;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.AssociatedFile;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.Group;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.ModelProject;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.ModelVersion;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.Parameter;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.Scenario;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.ScenarioCollection;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.SilverService;
import edu.cs.usask.ca.silver.businesslogic.commands.generic.User;
import edu.cs.usask.ca.silver.businesslogic.ids.BaseId;
import edu.cs.usask.ca.silver.businesslogic.ids.UserID;
import edu.cs.usask.ca.silver.definitions.SupportedModelType;
import edu.cs.usask.ca.silver.definitions.SystemProperties;
import edu.cs.usask.ca.silver.exceptions.UnableToInitializeStorageException;
import edu.cs.usask.ca.silver.plugin.rcp.Activator;
import edu.cs.usask.ca.silver.plugin.rcp.components.SilverPreferenceEntries;
import edu.cs.usask.ca.silver.plugin.rcp.components.SilverTreeObject;
import edu.cs.usask.ca.silver.plugin.rcp.jobs.LoggerReportScheduler;
import edu.cs.usask.ca.silver.plugin.rcp.views.projectviewer.ProjectViewI;

/**
 * SilverBusinessLogicController will be in charge of managing business object
 * pool.
 * 
 * @author Yudi Xue
 * 
 */
public class SilverBusinessLogicController {

	private static final LinkedList<SilverServiceI> stackOfBorrowedGenericSilverService = new LinkedList<SilverServiceI>();
	private static final LinkedList<UserI> stackOfBorrowedGenericUser = new LinkedList<UserI>();
	private static final LinkedList<GroupI> stackOfBorrowedGenericGroup = new LinkedList<GroupI>();
	private static final LinkedList<ModelProjectI> stackOfBorrowedGenericModelProject = new LinkedList<ModelProjectI>();
	private static final LinkedList<ModelVersionI> stackOfBorrowedGenericModelVersion = new LinkedList<ModelVersionI>();
	private static final LinkedList<ScenarioCollectionI> stackOfBorrowedGenericScenarioCollection = new LinkedList<ScenarioCollectionI>();
	private static final LinkedList<ScenarioI> stackOfBorrowedGenericScenario = new LinkedList<ScenarioI>();
	private static final LinkedList<ParameterI> stackOfBorrowedGenericParameter = new LinkedList<ParameterI>();
	private static final LinkedList<AssociatedFileI> stackOfBorrowedGenericAssociatedFile = new LinkedList<AssociatedFileI>();
	private static Logger logger = Logger
			.getLogger(SilverBusinessLogicController.class);

	private static BCodec myBCodec = new BCodec();

	// //////////////////////////////////////////////////////////
	// // public methods
	// //
	// //
	// //////////////////////////////////////////////////////////

	public static StorageI getLocalDerbyStorage() throws UnableToInitializeStorageException {
		StorageI derby = null;
		try {
			derby = borroweSilverServiceObject().getLocalDerbyStorage(
					SystemProperties.USER_HOME
							+ SystemProperties.FILE_SEPARATOR + "silverdb"
							+ SystemProperties.FILE_SEPARATOR + "db2",
					"silver", "silver", 60, 100);
			returnlastSilverServiceObject();
		} catch (UnableToInitializeStorageException ite)  {
			throw ite;
		}
		catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"RCP Exception", e);
		}
		return derby;
	}

	public static StorageI getRemoteDerbyStorage(String serverLoc,
			String databaseName, int port, String user, String pass) throws UnableToInitializeStorageException {
		StorageI derby = null;
		try {
			// Throws AtomikosSQLException  
			// returns null if failed to connect to database
			derby = borroweSilverServiceObject().getRemoteDerbyStorage(
					serverLoc, databaseName, port, user, pass, 60, 100);
			returnlastSilverServiceObject();
		} catch (UnableToInitializeStorageException ite) {
			throw ite;
		}
		catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"Storage Exception", e);
		}
		return derby;
	}

	public static StorageI createRemoteDerbyStorageFromDatabasePreference()
			throws NumberFormatException, UnableToInitializeStorageException, Exception {
		if (checkIfHasNoDatabaseConnectionInPreference()) {
			return getRemoteDerbyStorage(
					getPreferenceEntry(SilverPreferenceEntries.DATABASE_LOCATION),
					getPreferenceEntry(SilverPreferenceEntries.DATABASE_NAME),
					Integer.valueOf(
							getPreferenceEntry(SilverPreferenceEntries.DATABASE_PORT))
							.intValue(),
					getPreferenceEntry(SilverPreferenceEntries.DATABASE_USER),
					getPreferenceEntry(SilverPreferenceEntries.DATABASE_PASSWORD));
		}
		return null;
	}

	public static boolean loadVensimDLL(String libPath) {
		boolean result = false;
		File file = new File(libPath);
		if (SystemProperties.OS_NAME.startsWith("Windows") && file.canRead()) {
			try {
				borroweSilverServiceObject().loadVenDll(libPath);
			} catch (Exception e) {
				LoggerReportScheduler.scheduleErrorLogging(logger, "Error loading Vensim Library", e);
			}
			result = true;
			returnlastSilverServiceObject();
		}

		return result;
	}

	public static void returnlastSilverServiceObject() {
		if (!stackOfBorrowedGenericSilverService.isEmpty())
			SilverService.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericSilverService.removeLast());
	}

	public static void returnAllSilverServiceObject() {
		while (!stackOfBorrowedGenericSilverService.isEmpty())
			SilverService.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericSilverService.removeLast());
	}

	public static AssociatedFileI borrowFileObject() {
		stackOfBorrowedGenericAssociatedFile
				.addLast((AssociatedFile) AssociatedFile.getObjectPoolFactory()
						.borrowObject());
		return stackOfBorrowedGenericAssociatedFile.getLast();
	}

	public static SilverServiceI borroweSilverServiceObject() {
		stackOfBorrowedGenericSilverService
				.addLast((SilverService) SilverService.getObjectPoolFactory()
						.borrowObject());
		return stackOfBorrowedGenericSilverService.getLast();
	}

	public static void returnLastFileObject() {
		if (!stackOfBorrowedGenericAssociatedFile.isEmpty())
			AssociatedFile.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericAssociatedFile.removeLast());
	}

	public static void returnAllFileObject() {
		while (!stackOfBorrowedGenericAssociatedFile.isEmpty())
			returnLastFileObject();
	}

	public static UserI borrowUserObject() {
		stackOfBorrowedGenericUser.addLast((User) User.getObjectPoolFactory()
				.borrowObject());
		return stackOfBorrowedGenericUser.getLast();
	}

	public static void returnLastUserObject() {
		if (!stackOfBorrowedGenericUser.isEmpty())
			User.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericUser.removeLast());
	}

	public static void returnAllUserObject() {
		while (!stackOfBorrowedGenericUser.isEmpty()) {
			returnLastUserObject();
		}
	}

	public static GroupI borrowGroupObject() {
		stackOfBorrowedGenericGroup.addLast((GroupI) Group
				.getObjectPoolFactory().borrowObject());
		return stackOfBorrowedGenericGroup.getLast();
	}

	public static void returnLastGroupObject() {
		if (!stackOfBorrowedGenericGroup.isEmpty())
			Group.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericGroup.removeLast());
	}

	public static void returnAllGroupObject() {
		while (!stackOfBorrowedGenericGroup.isEmpty()) {
			returnLastGroupObject();
		}
	}

	public static ModelProjectI borrowMpObject() {
		stackOfBorrowedGenericModelProject.addLast((ModelProject) ModelProject
				.getObjectPoolFactory().borrowObject());
		return stackOfBorrowedGenericModelProject.getLast();
	}

	public static void returnLastMpObject() {
		if (!stackOfBorrowedGenericModelProject.isEmpty())
			ModelProject.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericModelProject.removeLast());

	}

	public static void returnAllMpObject() {
		while (!stackOfBorrowedGenericModelProject.isEmpty()) {
			returnLastMpObject();
		}
	}

	public static ModelVersionI borrowMvObject() {
		stackOfBorrowedGenericModelVersion.addLast((ModelVersion) ModelVersion
				.getObjectPoolFactory().borrowObject());
		return stackOfBorrowedGenericModelVersion.getLast();
	}

	public static void returnLastMvObject() {
		if (!stackOfBorrowedGenericModelVersion.isEmpty())
			ModelVersion.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericModelVersion.removeLast());
	}

	public static void returnAllMvObject() {
		while (stackOfBorrowedGenericModelVersion.isEmpty()) {
			returnLastMvObject();
		}
	}

	public static ScenarioCollectionI borrowScObject() {
		stackOfBorrowedGenericScenarioCollection
				.addLast((ScenarioCollection) ScenarioCollection
						.getObjectPoolFactory().borrowObject());
		return stackOfBorrowedGenericScenarioCollection.getLast();
	}

	public static void returnLastScObject() {
		if (!stackOfBorrowedGenericScenarioCollection.isEmpty())
			ScenarioCollection.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericScenarioCollection.removeLast());
	}

	public static void returnAllScObject() {
		while (!stackOfBorrowedGenericScenarioCollection.isEmpty()) {
			returnLastScObject();
		}
	}

	public static ScenarioI borrowScenario() {
		stackOfBorrowedGenericScenario.addLast((Scenario) Scenario
				.getObjectPoolFactory().borrowObject());
		return stackOfBorrowedGenericScenario.getLast();
	}

	public static void returnLastScenarioObject() {
		if (!stackOfBorrowedGenericScenario.isEmpty())
			Scenario.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericScenario.removeLast());
	}

	public static void returnAllScenarioObject() {
		while (!stackOfBorrowedGenericScenario.isEmpty()) {
			returnLastScenarioObject();
		}
	}

	public static ParameterI borrowParameterObject() {
		stackOfBorrowedGenericParameter.addLast((Parameter) Parameter
				.getObjectPoolFactory().borrowObject());
		return stackOfBorrowedGenericParameter.getLast();
	}

	public static void returnLastParameterObject() {
		if (!stackOfBorrowedGenericParameter.isEmpty())
			Parameter.getObjectPoolFactory().returnObject(
					stackOfBorrowedGenericParameter.removeLast());
	}

	public static void returnAllParameterObject() {
		while (!stackOfBorrowedGenericParameter.isEmpty()) {
			returnLastParameterObject();
		}
	}

	/**
	 * 
	 * @return UserId the login id
	 * @throws NullPointerException
	 *             if no user can be fetched with the registered information,
	 *             throw NullpointerException
	 * @throws Exception
	 */
	public static UserID fetchUserIDfromDatabaseByPreferenceInfo(
			StorageI storage) {
		UserID returnedId = null;
		try {
			returnedId = SilverBusinessLogicController
					.borrowUserObject()
					.getUserIDFromEmailAndPassCombination(
							getPreferenceEntry(SilverPreferenceEntries.USER_EMAIL_LOGIN),
							getPreferenceEntry(SilverPreferenceEntries.USER_PASSWORD),
							storage);
		} catch (NullPointerException e) {
			try {
				returnedId = borrowUserObject()
						.createUser(
								getPreferenceEntry(SilverPreferenceEntries.USER_FIRST_NAME),
								getPreferenceEntry(SilverPreferenceEntries.USER_LAST_NAME),
								getPreferenceEntry(SilverPreferenceEntries.USER_EMAIL_LOGIN),
								getPreferenceEntry(SilverPreferenceEntries.USER_PASSWORD),
								storage);
			} catch (SQLDataException e1) {
				LoggerReportScheduler.scheduleErrorLogging(logger,"SQL Data Exception in RCP", e1);
			} catch (Exception e1) {
				LoggerReportScheduler.scheduleErrorLogging(logger,"Exception in RCP", e1);
			}
			LoggerReportScheduler.scheduleErrorLogging(logger,"Null Pointer Exception in RCP", e);
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"Exception in RCP", e);
		}

		if (returnedId.getUUIdVal() == null) {
			try {
				returnedId = borrowUserObject()
						.createUser(
								getPreferenceEntry(SilverPreferenceEntries.USER_FIRST_NAME),
								getPreferenceEntry(SilverPreferenceEntries.USER_LAST_NAME),
								getPreferenceEntry(SilverPreferenceEntries.USER_EMAIL_LOGIN),
								getPreferenceEntry(SilverPreferenceEntries.USER_PASSWORD),
								storage);
			} catch (SQLDataException e1) {
				LoggerReportScheduler.scheduleErrorLogging(logger,"SQL Exception in RCP", e1);
			} catch (Exception e1) {
				LoggerReportScheduler.scheduleErrorLogging(logger,"RCP Exception in RCP", e1);
			}

		}

		returnAllUserObject();

		return returnedId;
	}

	/**
	 * A method that returns the baseId for the parent of the current silver
	 * tree object This method is currently only being consider for the parent
	 * of a scenario collection but could be expanded to include other baseId
	 * types
	 * 
	 * @param objectId
	 * @param storage
	 *            Database instance used to extract user information 
	 * @return the parent baseID
	 */
	public static BaseId getParentBaseId(BaseId objectId, StorageI storage) {
		try {
			return objectId.getParentObjectId(storage);
		} catch (NullPointerException e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"SQL Exception", e);
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"RCP Exception", e);
		}
		return null;
	}

	/**
	 * Find out whether 64base encoded string exceeds 128 char.
	 * 
	 * @param shortStringInput
	 * @return True if not exceeded, false otherwise
	 */
	public static boolean checkExceeding128CharStringLength(
			String shortStringInput) {
		boolean result = false;
		try {
			if (myBCodec.encode(shortStringInput).length() > 128)
				result = true;
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"Error checking maximum length", e);
		}
		return result;
	}

	/**
	 * Find out whether 64base encoded string exceeds 500 char.
	 * 
	 * @param mediumStringInput
	 * @return True if not exceeded, false otherwise
	 */
	public static boolean checkExceeding500CharStringLength(
			String mediumStringInput) {
		boolean result = false;
		try {
			if (myBCodec.encode(mediumStringInput).length() > 500)
				result = true;
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"Error checking maximum length", e);
		}
		return result;
	}

	/**
	 * Find out whether 64base encoded string exceeds 1000 char.
	 * 
	 * @param longStringInput
	 * @return True if not exceeded, false otherwise
	 */
	public static boolean checkExceeding1000CharStringLength(
			String longStringInput) {
		boolean result = false;
		try {
			if (myBCodec.encode(longStringInput).length() > 1000)
				result = true;
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"Error checking maximum length", e);
		}
		return result;

	}

	public static void storeUserLoginInformation(String userfirstName,
			String userLastName, String userEmail, String userPass)
			throws NullPointerException {

		if (userfirstName == null || userLastName == null || userEmail == null
				|| userPass == null) {
			throw new NullPointerException();
		}

		setPreferenceEntry(SilverPreferenceEntries.USER_FIRST_NAME,
				userfirstName);
		setPreferenceEntry(SilverPreferenceEntries.USER_LAST_NAME, userLastName);
		setPreferenceEntry(SilverPreferenceEntries.USER_EMAIL_LOGIN, userEmail);
		setPreferenceEntry(SilverPreferenceEntries.USER_PASSWORD, userPass);

	}

	public static String getLocalDatabaseInformationFromPreference() {
		return getPreferenceEntry(SilverPreferenceEntries.LOCAL_DATABASE_LOCATION);
	}

	public static void saveLocalDatabaseLocationToPreference(String location) {
		setPreferenceEntry(SilverPreferenceEntries.LOCAL_DATABASE_LOCATION,
				location);
	}

	public static void writeNewDatabaseConnectionToPreference(String location,
			String databaseName, String databasePort, String databaseUser,
			String databasePass) throws NullPointerException {

		if (location == null || databaseName == null || databasePort == null
				|| databaseUser == null || databasePass == null) {
			throw new NullPointerException();
		}

		setPreferenceEntry(SilverPreferenceEntries.DATABASE_LOCATION, location);
		setPreferenceEntry(SilverPreferenceEntries.DATABASE_NAME, databaseName);
		setPreferenceEntry(SilverPreferenceEntries.DATABASE_PORT, databasePort);
		setPreferenceEntry(SilverPreferenceEntries.DATABASE_USER, databaseUser);
		setPreferenceEntry(SilverPreferenceEntries.DATABASE_PASSWORD,
				databasePass);

	}

	/**
	 * Check if there is remote database information in workspace preference.
	 * 
	 * @return true if the remote database information is stored in preference,
	 *         false if not
	 */
	public static boolean checkIfHasNoDatabaseConnectionInPreference() {
		List<String> a = new ArrayList<String>();

		a.add(getPreferenceEntry(SilverPreferenceEntries.DATABASE_LOCATION));
		a.add(getPreferenceEntry(SilverPreferenceEntries.DATABASE_NAME));
		a.add(getPreferenceEntry(SilverPreferenceEntries.DATABASE_PORT));
		a.add(getPreferenceEntry(SilverPreferenceEntries.DATABASE_USER));
		a.add(getPreferenceEntry(SilverPreferenceEntries.DATABASE_PASSWORD));

		for (String string : a) {
			if (string.length() == 0)
				return false;
		}
		return true;
	}

	/**
	 * Check if there is user information in workspace preference.
	 * 
	 * @return true if the user information is stored in preference, false if
	 *         not
	 */
	public static boolean checkIfHasUserInformationInPreference() {
		List<String> a = new ArrayList<String>();
		a.add(getPreferenceEntry(SilverPreferenceEntries.USER_EMAIL_LOGIN));
		a.add(getPreferenceEntry(SilverPreferenceEntries.USER_FIRST_NAME));
		a.add(getPreferenceEntry(SilverPreferenceEntries.USER_LAST_NAME));
		a.add(getPreferenceEntry(SilverPreferenceEntries.USER_PASSWORD));

		for (String string : a) {
			if (string.length() == 0)
				return false;
		}
		return true;
	}

	public static String getPreferenceEntry(SilverPreferenceEntries entry) {
		return Activator.getDefault().getPreferenceStore()
				.getString(entry.name());
	}

	public static void setPreferenceEntry(SilverPreferenceEntries entry,
			String val) {
		Activator.getDefault().getPreferenceStore().setValue(entry.name(), val);
	}

	private static final SupportedModelType[] values = SupportedModelType
			.values();

	public static SupportedModelType[] getSupportedModelTypes() {
		return values;
	}

	// //////////////////////////////////////////////////////////
	// // Helper methods
	// //
	// //////////////////////////////////////////////////////////

	private static String getLoggedInUserEmail() throws Exception {
		return getPreferenceEntry(SilverPreferenceEntries.USER_EMAIL_LOGIN);
	}

	private static boolean verifyUserIdentityWithEmailInDatabase(UserID userId,
			String uEmail, StorageI storage) {
		boolean result = false;
		try {
			String email = SilverBusinessLogicController.borrowUserObject()
					.getEmail(userId, storage);
			result = (email.compareTo(uEmail) == 0) ? true : false;
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,"RCP Exception", e);
		}
		return result;
	}

	private static void setTreeUserIDInput(UserID userId, IWorkbench workbench) {
		SilverTreeObject newRoot = new SilverTreeObject(userId);
		// TODO find all views.
		ProjectViewI a = (ProjectViewI) workbench.getActiveWorkbenchWindow()
				.getActivePage().findView("123");
		a.resetTreeRoot(newRoot);
	}

}
